/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.rafkind.resrev.web.page.component;

import com.rafkind.resrev.dao.ReservationDao;
import com.rafkind.resrev.data.Color;
import com.rafkind.resrev.data.Reservation;
import com.rafkind.resrev.data.Status;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.behavior.HeaderContributor;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.IChoiceRenderer;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.GridView;
import org.apache.wicket.markup.repeater.data.IDataProvider;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.spring.injection.annot.SpringBean;

/**
 *
 * @author dave
 */
public class CalendarPanel extends Panel {
  
  @SpringBean
  private ReservationDao reservationDao;
  
  private String[] months;  
  private int[] years;
  private String[] days;
  
  private Model<CalendarDataProvider> calendarDataModel;
  
  public CalendarPanel(String id) {    
    super(id);
    
    add(HeaderContributor.forCss(CalendarPanel.class, "CalendarPanel.css"));
    
    initDaysMonthsAndYears();
    
    setOutputMarkupId(true);
    
    CalendarDataProvider cdp = 
        new CalendarDataProvider(days, years);
    
    calendarDataModel = new Model<CalendarDataProvider>(cdp);    
   
    final GridView<Day> calendarGrid = new GridView<Day>("calendarRows", cdp) {
      @Override
      protected void populateEmptyItem(Item<Day> item) {        
        item.add(new Label("calendarDayBox", "*empty*"));
      }

      @Override
      protected void populateItem(Item<Day> item) {
        Day d = item.getModelObject();
        item.add(new CalendarDayBox("calendarDayBox", new Model<Day>(d)));
        
        if (d.isLastMonth())
          item.add(new SimpleAttributeModifier("class", "lastMonth"));
        else if (d.isNextMonth())
          item.add(new SimpleAttributeModifier("class", "nextMonth"));
        else if (d.isToday())
          item.add(new SimpleAttributeModifier("class", "today"));
        else if (d.isPast())
          item.add(new SimpleAttributeModifier("class", "past"));
        else
          item.add(new SimpleAttributeModifier("class", "future"));
      }      
    };
    calendarGrid.setOutputMarkupId(true);
    calendarGrid.setOutputMarkupPlaceholderTag(true);
    
    calendarGrid.setColumns(7);
    calendarGrid.setRows(6);
    add(calendarGrid);
    
    final DropDownChoice monthChoice = 
        new DropDownChoice(
          "monthChoice", 
          new PropertyModel(calendarDataModel, "currentMonth"), 
          createList(0, months.length),
          new IChoiceRenderer(){
            public Object getDisplayValue(Object value) {
              Integer index = (Integer) value;
              return months[index];
            }

            public String getIdValue(Object value, int index) {
              return Integer.toString(index);
            }      
          }){
      @Override
      protected boolean wantOnSelectionChangedNotifications() {
        return true;
      }      
    };
        
    
    add(monthChoice);
    monthChoice.setOutputMarkupId(true);
    monthChoice.setOutputMarkupPlaceholderTag(true);    
    
    final DropDownChoice yearChoice = new DropDownChoice("yearChoice", 
        new PropertyModel(calendarDataModel, "currentYearIndex"),
        createList(0, years.length),
        new IChoiceRenderer(){
          public Object getDisplayValue(Object value) {
            Integer index = (Integer) value;
            return years[index];
          }
          
          public String getIdValue(Object value, int index) {
            return Integer.toString(index);
          }
        }){

      @Override
      protected boolean wantOnSelectionChangedNotifications() {
        return true;
      }          
    };
    
    add(yearChoice);
    yearChoice.setOutputMarkupId(true);
    yearChoice.setOutputMarkupPlaceholderTag(true);
    
    
    add(new AjaxFallbackLink("prevYear"){
      @Override
      public void onClick(AjaxRequestTarget target) {
        CalendarDataProvider cdp = (CalendarDataProvider)
            calendarDataModel.getObject();
        
        cdp.goPrevYear();
            
        target.addComponent(CalendarPanel.this);
      }
      @Override
      public boolean isEnabled() {
        CalendarDataProvider cdp = (CalendarDataProvider)
            calendarDataModel.getObject();
        
        return cdp.getCurrentYearIndex() > 0;
      }      
    });
    
    add(new AjaxFallbackLink("nextYear"){
      @Override
      public void onClick(AjaxRequestTarget target) {
        CalendarDataProvider cdp = (CalendarDataProvider)
            calendarDataModel.getObject();
        
        cdp.goNextYear();        
        target.addComponent(CalendarPanel.this);
      }
      
      @Override
      public boolean isEnabled() {
        CalendarDataProvider cdp = (CalendarDataProvider)
            calendarDataModel.getObject();
        
        return cdp.getCurrentYearIndex() < years.length-1;
      }
    });
    
    add(new AjaxFallbackLink("prevMonth") {
      @Override
      public void onClick(AjaxRequestTarget target) {
        CalendarDataProvider cdp = (CalendarDataProvider)
            calendarDataModel.getObject();
        
        cdp.goPrevMonth();
        target.addComponent(CalendarPanel.this);
      }
      @Override
      public boolean isEnabled() {
        CalendarDataProvider cdp = (CalendarDataProvider)
            calendarDataModel.getObject();
        
        return cdp.getCurrentYearIndex() > 0 
            || cdp.getCurrentMonth() > 0;
      }
    });   
    
    add(new AjaxFallbackLink("nextMonth") {
      @Override
      public void onClick(AjaxRequestTarget target) {
        CalendarDataProvider cdp = (CalendarDataProvider)
            calendarDataModel.getObject();
        
        cdp.goNextMonth();
        target.addComponent(CalendarPanel.this);
      }
      
      @Override
      public boolean isEnabled() {
        CalendarDataProvider cdp = (CalendarDataProvider)
            calendarDataModel.getObject();
        
        return cdp.getCurrentYearIndex() < years.length-1 
            || cdp.getCurrentMonth() < months.length-1;
      }
    });
  }
  
  private void initDaysMonthsAndYears() {
    Calendar c = Calendar.getInstance();
    
    years = new int[21];
    for (int i = -10; i <= 10; i++) {
      years[i+10] = c.get(Calendar.YEAR) + i;
    }
    
    months = new String[12];
    Locale locale = Locale.getDefault();
    for (int i = 0; i < months.length; i++) {
      c.set(Calendar.MONTH, i);
      months[i] = c.getDisplayName(Calendar.MONTH, Calendar.LONG, locale);
    }
    
    days = new String[7];
    for (int i = 0; i < days.length; i++) {
      c.set(Calendar.DAY_OF_WEEK, i+1);
      days[i] = c.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, locale);
    }
  } 
  
  public static class Day implements Serializable {    
    
    private int day;
    private int month;
    private int year;
    private boolean lastMonth;
    private boolean nextMonth;
    private boolean past;
    private boolean today;
    private String name;
    private List<StyledMessage> messages;

    public Day() {
      messages = new ArrayList<StyledMessage>();
    }   

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }
    public boolean isToday() {
      return today;
    }

    public void setToday(boolean today) {
      this.today = today;
    }

    public int getDay() {
      return day;
    }

    public void setDay(int day) {
      this.day = day;
    }

    public boolean isLastMonth() {
      return lastMonth;
    }

    public void setLastMonth(boolean lastMonth) {
      this.lastMonth = lastMonth;
    }

    public int getMonth() {
      return month;
    }

    public void setMonth(int month) {
      this.month = month;
    }

    public boolean isNextMonth() {
      return nextMonth;
    }

    public void setNextMonth(boolean nextMonth) {
      this.nextMonth = nextMonth;
    }

    public boolean isPast() {
      return past;
    }

    public void setPast(boolean past) {
      this.past = past;
    }

    public int getYear() {
      return year;
    }

    public void setYear(int year) {
      this.year = year;
    }      
    
    private static SimpleDateFormat sdf = new SimpleDateFormat("hh:mm aa");
    public void tryToAddMessageFor(Reservation reservation) {
      Calendar c = new GregorianCalendar(year, month, day, 0, 0, 0);
      
      Date start = c.getTime();
      
      c.add(Calendar.DATE, 1);
      c.add(Calendar.SECOND, -1);
      
      Date end = c.getTime();
      
      if (reservation.overlapsInterval(start, end)) {
        String style = null;
        String message = null;
        if (reservation.getReserver().getColor().equals(Color.DEFAULT))
          style = "color: " + reservation.getResource().getColor().getHexColor();
        else
          style = "color: " + reservation.getReserver().getColor().getHexColor();
        
        if (!reservation.getAuthorization().equals(Status.APPROVED))
          style += "; font-style: italic";
        
        if (start.before(reservation.getStart())) {
          if (end.after(reservation.getEnd())) {
            // start and end today
            message = reservation.getReserver().getName() 
                + " has " 
                + reservation.getResource()
                + " from "
                + sdf.format(reservation.getStart())
                + " to "
                + sdf.format(reservation.getEnd());
          } else {
            // start today, end tomorrow or later
            message = reservation.getReserver().getName() 
                + " has " 
                + reservation.getResource()
                + " from "
                + sdf.format(reservation.getStart());                
          }
        } else if (end.after(reservation.getEnd())) {
          // start yesterday or before, end today
          message = reservation.getReserver().getName() 
                + " has " 
                + reservation.getResource()                
                + " until "
                + sdf.format(reservation.getEnd());
        } else {
          // start yesterday or before, end tomorrow or later
          message = reservation.getReserver().getName() 
                + " still has " 
                + reservation.getResource();
                
        }
        messages.add(new StyledMessage(message, style));
      }
    }

    public List<StyledMessage> getMessages() {
      return messages;
    }
    
    public static class StyledMessage {
      private String message;
      private String style;

      public StyledMessage(String message, String style) {
        this.message = message;
        this.style = style;
      }

      public String getStyle() {
        return style;
      }

      public String getMessage() {
        return message;
      }     
    }
  }
  
  public static class DayIterator implements Iterator<Day> {
    private Day[] days;
    private int first;
    private int count;
    private int current;

    public DayIterator(Day[] days, int first, int count) {
      this.days = days;
      this.first = first;
      this.count = count;
      current = 0;
    }

    public boolean hasNext() {
      return current < count;
    }

    public Day next() {
      Day d = days[first+current];
      current++;
      return d;
    }

    public void remove() {
      throw new UnsupportedOperationException("Not supported yet.");
    }
    
    
  }
  
  public class CalendarDataProvider implements IDataProvider<Day> {    
    private Day[] days;    
    
    private int currentYearIndex;
    private int currentMonth;
    
    private String[] dayNames;
    private int[] yearsAvailable;

    public CalendarDataProvider(String[] dayNames, int[] yearsAvailable) {
      Calendar c = Calendar.getInstance();
      
      //currentYear = c.get(Calendar.YEAR);      
      for (int i = 0; i < yearsAvailable.length; i++)
        if (yearsAvailable[i] == c.get(Calendar.YEAR))
          currentYearIndex = i;
      
      currentMonth = c.get(Calendar.MONTH);
      
      this.dayNames = dayNames;
      this.yearsAvailable = yearsAvailable;
      
      createDays();
    }
    
    public int getCurrentMonth() {
      return currentMonth;
    }

    public void setCurrentMonth(int currentMonth) {
      this.currentMonth = currentMonth;
      createDays();
    }
    
    public int getCurrentYearIndex() {
      return currentYearIndex;
    }
    
    public void setCurrentYearIndex(int i) {      
      currentYearIndex = i;
      createDays();
    }
    
    public Calendar getFirstDay() {
      Calendar c = Calendar.getInstance();
      c.set(Calendar.YEAR, yearsAvailable[currentYearIndex]);
      c.set(Calendar.MONTH, currentMonth);
      c.set(Calendar.DATE, 1);
      c.set(Calendar.HOUR_OF_DAY, 0);
      c.set(Calendar.MINUTE, 0);
      c.set(Calendar.SECOND, 0);
      c.set(Calendar.MILLISECOND, 0);
      
      c.getTime(); // jolt it into updating
      
      return c;
    }
    
    public Calendar getLastDay() {
      Calendar c = getFirstDay();
      c.add(Calendar.MONTH, 1);
      c.add(Calendar.SECOND, -1);
      
      c.getTime();
      
      return c;
    }
    
    public void goPrevYear() {
      currentYearIndex--;
      createDays();
    }
    
    public void goNextYear() {
      currentYearIndex++;
      createDays();
    }
    
    public void goPrevMonth() {
      currentMonth--;
      if (currentMonth < 0) {
        currentMonth = 11;
        currentYearIndex--;
      }
      createDays();
    }
    
    public void goNextMonth() {
      currentMonth++;
      if (currentMonth > 11) {
        currentMonth = 0;
        currentYearIndex++;
      }
      createDays();
    }
    
    private boolean isLastMonthOrBefore(Calendar target, Calendar now) {
      if (target.get(Calendar.YEAR) == now.get(Calendar.YEAR)) {
        return (target.get(Calendar.MONTH) < now.get(Calendar.MONTH));
      } else if (target.get(Calendar.YEAR) < now.get(Calendar.YEAR)) {
        return true;
      } else 
        return false;
    }
    
    private boolean isNextMonthOrAfter(Calendar target, Calendar now) {
      if (target.get(Calendar.YEAR) == now.get(Calendar.YEAR)) {
        return (target.get(Calendar.MONTH) > now.get(Calendar.MONTH));
      } else if (target.get(Calendar.YEAR) > now.get(Calendar.YEAR)) {
        return true;
      } else 
        return false;
    }
    
    private boolean isToday(Calendar target, Calendar now) {
      return target.get(Calendar.YEAR) == now.get(Calendar.YEAR) 
          && target.get(Calendar.MONTH) == now.get(Calendar.MONTH)
          && target.get(Calendar.DATE) == now.get(Calendar.DATE);
    }
    
    private void createDays() {
      Calendar now = Calendar.getInstance();
      
      Calendar working = getFirstDay();

      //working.set(Calendar.DAY_OF_MONTH, 1);
      //working.getTime(); // seems to jolt system into updating
      
      working.set(Calendar.DAY_OF_WEEK, 1);
      working.getTime(); // jolt it again
      
      List<Reservation> reservations = reservationDao.findReservations(
          getFirstDay().getTime(),
          getLastDay().getTime());      
      
      days = new Day[42];      
      
      for (int i = 0; i < 42; i++) {
        
        Day d = new Day();
        d.setDay(working.get(Calendar.DAY_OF_MONTH));
        d.setMonth(working.get(Calendar.MONTH));
        d.setYear(working.get(Calendar.YEAR));
        
        if (working.before(now)) {
          d.setPast(true);
          d.setLastMonth(isLastMonthOrBefore(working, now));
          d.setNextMonth(false);
        } else {
          d.setPast(false);
          d.setNextMonth(isNextMonthOrAfter(working, now));
          d.setLastMonth(false);
        }
        d.setToday(isToday(working, now));
        
        if (i < 7)
          d.setName(dayNames[i]);
        
        for (Reservation r : reservations)
          d.tryToAddMessageFor(r);
        
        days[i] = d;
        working.add(Calendar.DATE, 1);
      }
    }

    public Iterator<? extends Day> iterator(int first, int count) {
      return new DayIterator(days, first, count);
    }

    public IModel<Day> model(Day day) {
      return new Model(day);
    }

    public int size() {
      return days.length;
    }

    public void detach() {      
    }   
  }
  
  private static List<Integer> createList(int from, int to) {
    ArrayList<Integer> list = new ArrayList<Integer>(Math.max(to, from)-Math.min(to, from));
    
    if (to != from) {
      int start = from;
      int dir = (to-from) / Math.abs(to-from);
      
      while (start != to) {
        list.add(start);
        start += dir;
      }
    }
    
    return list;
  }
}